ποΈGitΠ―ΡΠ°ποΈ
Node / meshtastic / Meshtastic-Android / files / feature / firmware / src / commonMain / kotlin / org / meshtastic / feature / firmware / MaintenanceUf2.kt
Displaying Raw β’ Download
feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/MaintenanceUf2.kt 4d4070c8e1ed73a56ee2f054300a0f62e3fe2e62 (4d4070c8) Text, 17.13 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.firmware
Tff7b72import T7ee787org.meshtastic.core.model.DeviceHardware
Tff7b72import T7ee787org.meshtastic.core.model.SoftDeviceVariant
T8b949e/**
* A pinned, content-verified UF2 image used by a maintenance flow (factory erase, bootloader upgrade).
*
* Unlike release firmware β which `FirmwareRetriever` resolves against a versioned release folder β these images have
* no versioned upstream. `meshtastic/nrf52_factory_erase` has cut no GitHub releases, so the flasher serves them from a
* mutable `public/uf2/` path. Pinning the URL to a commit and verifying [sha256] before any write is what turns that
* mutable path back into an immutable one.
*
* @property expectedFirstTargetAddress For nRF erase images, the flash address the UF2's first block writes to. Checked
* against the resolved [SoftDeviceVariant] before the image is offered, because a swapped URL/digest row is the one
* authoring mistake a digest alone cannot catch β and the mistake that corrupts a SoftDevice. Null when the image's
* address carries no such invariant (RP2040, bootloader self-updates).
*/
Tff7b72internal Tff7b72data Tff7b72class T56d364MaintenanceUf2Tb4b4b4(
Tff7b72val Te6edf3urlTb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3fileNameTb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3sha256Tb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3expectedFirstTargetAddressTb4b4b4: Tffa657Long? Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72init Tb4b4b4{
T8b949e// downloadFile interpolates fileName straight into a temp path; these are compile-time constants, but assert it
T8b949e// so a future edit can't introduce traversal.
Te6edf3requireTb4b4b4(Te6edf3fileNameTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72& Te6edf3fileNameTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657it Tff7b72=Tff7b72= Ta5d6ff'/' Tff7b72|Tff7b72| Tffa657it Tff7b72=Tff7b72= Ta5d6ff'\\' Tb4b4b4} Tff7b72&Tff7b72& Tff7b72!Te6edf3fileNameTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ff..Ta5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4{
Ta5d6ff"Ta5d6ffUnsafe maintenance UF2 filename: Tffd700$Te6edf3fileNameTa5d6ff"
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Commit-pinned base for the factory-erase images checked into `meshtastic/web-flasher` at `public/uf2/`.
*
* The blobs have not changed since this commit (2024-09-03), so the pin is cheap to hold. Built from
* `meshtastic/nrf52_factory_erase` (GPL-3.0).
*/
Tff7b72private Tff7b72const Tff7b72val Te6edf3ERASE_UF2_BASE Tff7b72=
Ta5d6ff"Ta5d6ffhttps://raw.githubusercontent.com/meshtastic/web-flasher/0e353b5d0756c9a1b76f53be78e948fafc1ebd8a/public/uf2Ta5d6ff"
T8b949e/** Release-pinned base for the OTAFIX bootloader self-update images (`meshtastic/β¦_OTAFIX`, MIT). */
Tff7b72private Tff7b72const Tff7b72val Te6edf3OTAFIX_RELEASE_TAG Tff7b72= Ta5d6ff"Ta5d6ff0.9.2-OTAFIX2.3-BP1.5Ta5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3OTAFIX_BASE Tff7b72=
Ta5d6ff"Ta5d6ffhttps://github.com/meshtastic/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/Tffd700$Te6edf3OTAFIX_RELEASE_TAGTa5d6ff"
T8b949e/** S140 6.1.1 application start β the address `nrf_erase2.uf2` is linked for. */
Tff7b72internal Tff7b72const Tff7b72val Te6edf3APP_START_S140_6_1_1 Tff7b72= T79c0ff0Te6edf3x26000L
T8b949e/** S140 7.3.0 application start β the address `nrf_erase_sd7_3.uf2` is linked for. */
Tff7b72internal Tff7b72const Tff7b72val Te6edf3APP_START_S140_7_3_0 Tff7b72= T79c0ff0Te6edf3x27000L
Tff7b72private Tff7b72val Te6edf3ERASE_S140_6_1_1 Tff7b72=
Te6edf3MaintenanceUf2Tb4b4b4(
Te6edf3url Tff7b72= Ta5d6ff"Tffd700$Te6edf3ERASE_UF2_BASETa5d6ff/nrf_erase2.uf2Ta5d6ff"Tb4b4b4,
Te6edf3fileName Tff7b72= Ta5d6ff"Ta5d6ffnrf_erase2.uf2Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ff4b778a3def19854415db64cb51bfd29c15b11cc46006353dd518f62d09efe3feTa5d6ff"Tb4b4b4,
Te6edf3expectedFirstTargetAddress Tff7b72= Te6edf3APP_START_S140_6_1_1Tb4b4b4,
Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3ERASE_S140_7_3_0 Tff7b72=
Te6edf3MaintenanceUf2Tb4b4b4(
Te6edf3url Tff7b72= Ta5d6ff"Tffd700$Te6edf3ERASE_UF2_BASETa5d6ff/nrf_erase_sd7_3.uf2Ta5d6ff"Tb4b4b4,
Te6edf3fileName Tff7b72= Ta5d6ff"Ta5d6ffnrf_erase_sd7_3.uf2Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ff13941bedce009e61255c37b1524d11ca604e88c38e7588bb8b391e2998da468fTa5d6ff"Tb4b4b4,
Te6edf3expectedFirstTargetAddress Tff7b72= Te6edf3APP_START_S140_7_3_0Tb4b4b4,
Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3PICO_ERASE Tff7b72=
Te6edf3MaintenanceUf2Tb4b4b4(
Te6edf3url Tff7b72= Ta5d6ff"Tffd700$Te6edf3ERASE_UF2_BASETa5d6ff/pico_erase.uf2Ta5d6ff"Tb4b4b4,
Te6edf3fileName Tff7b72= Ta5d6ff"Ta5d6ffpico_erase.uf2Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ff08aa7d561e8b8bf2f9b061b3506fb4d8f135e832efe0f3ae978241db2da0c853Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffotafixAssetTb4b4b4(Te6edf3boardTb4b4b4: Tffa657StringTb4b4b4, Te6edf3sha256Tb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3MaintenanceUf2 Tb4b4b4{
Tff7b72val Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffupdate-Tffd700${Te6edf3boardTffd700}Ta5d6ff_bootloader-Tffd700${Te6edf3OTAFIX_RELEASE_TAGTffd700}Ta5d6ff_nosd.uf2Ta5d6ff"
Tff7b72return Te6edf3MaintenanceUf2Tb4b4b4(Te6edf3url Tff7b72= Ta5d6ff"Tffd700$Te6edf3OTAFIX_BASETa5d6ff/Tffd700$Te6edf3nameTa5d6ff"Tb4b4b4, Te6edf3fileName Tff7b72= Te6edf3nameTb4b4b4, Te6edf3sha256 Tff7b72= Te6edf3sha256Tb4b4b4)
Tb4b4b4}
T8b949e/**
* OTAFIX bootloader self-update images, keyed by the **Board-ID the device itself reports**.
*
* Every Adafruit-family bootloader writes `INFO_UF2.TXT` to its mass-storage volume containing a `Board-ID:` line
* (`ghostfat.c`, from each board's `UF2_BOARD_ID`). Reading that line off the mounted drive identifies the hardware
* authoritatively, so nothing here depends on correlating names between the two projects β which is impossible anyway:
* `heltec_t114` vs `heltec-mesh-node-t114`, `thinknode_m1` vs `ThinkNode-M1`, `t1000_e` vs `tracker-t1000-e`.
*
* USB VID/PID would not do: four OTAFIX boards share `239A/0029`, all three ThinkNodes share `239A/00DA`, and SenseCAP
* Solar P1 collides with XIAO nRF52840 BLE on `2886/0044`. Board-IDs are unique across all 14, which a test asserts.
*
* This is also the only way to resolve the XIAO nRF52840 BLE / BLE Sense split that OTAFIX's own README calls out: the
* two differ only by Board-ID, and installing the wrong one over UF2 is exactly what that note warns against.
*/
Tff7b72private Tff7b72val Te6edf3OTAFIX_BY_BOARD_IDTb4b4b4: Te6edf3MapTff7b72<Tffa657StringTb4b4b4, Te6edf3MaintenanceUf2Tff7b72> Tff7b72=
Te6edf3mapOfTb4b4b4(
Ta5d6ff"Ta5d6ffHT-n5262Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffheltec_t114Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ffae92d3577cb58dd9b43c9b61ffb9bfffda05b0eca4113a0ec42a37cd8be53b19Ta5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffMinewSemi-MX25LE01Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffminewsemi_mx25le01Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ffe09564fd8dd03fc25d76dcb732a0214c79653da3b130240949b783254d3dfc1bTa5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffTRACKER L1Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffwio_tracker_l1Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ff70fbce0eda9d70d7bd8a4367057badf5ec310838bf3221370d45a56f04956b9eTa5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffWisBlock-RAK4631-BoardTa5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffwiscore_rak4631_boardTa5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ff8741bc677a3c24f28422c5ffb80761de7d98a127a3b0191ba6585bf57ce9f305Ta5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffWisMesh-TagTa5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffwismesh_tagTa5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ff96d42e1990e17251e8c625e98a1551cac12c6e29111bc2e59ab7c9fe6dec8758Ta5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffnRF52840-SeeedSenseCAPSolarP1-v1Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffsensecap_solar_p1Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ff9b4bce48c1b4830617715c5619457bce6b21f3079803e35e13433de7701290f5Ta5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffnRF52840-SeeedXiao-v1Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffxiao_nrf52840_bleTa5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ffff8a0916e98cceb394fd66590bccc17f63612c11ff56b086ef88bd436c8df67fTa5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffnRF52840-SeeedXiaoSense-v1Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffxiao_nrf52840_ble_senseTa5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6fffc233d83a1011419625fcb50b49084578460c25bbc0270374ca176757a3c40daTa5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffnRF52840-T1000-E-v1Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(Te6edf3board Tff7b72= Ta5d6ff"Ta5d6fft1000_eTa5d6ff"Tb4b4b4, Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ff5c065e11b8acd5b0cefa9295f98bca1512306cfa478856aa76a871124a904cc4Ta5d6ff"Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffnRF52840-TEcho-v1Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6fflilygo_techoTa5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ff2ddb36188ffe521c270bb2ce8441d742d0fe45325c57e4db6475bf63162a59b0Ta5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffnRF52840-ThinkNode-M3-v1Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffthinknode_m3Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ffbf90979f2f6adc96ef6ca09c280b2ab7e66cb8ce2654fc80da9b20407bfb8708Ta5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffnRF52840-ThinkNodeM1-v1Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffthinknode_m1Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ffaa0721b573c60e0b179274d5a5296bac7a8436faf339cfc03116ebe8a4375795Ta5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffnRF52840-ThinkNodeM6-v1Ta5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffthinknode_m6Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ffaaf94953a540a18f3e48f4cdec0c78290ad3c5f8740aea26fa3b3ce3632a8d4aTa5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffnRF52840-promicroTa5d6ff" Te6edf3to
Te6edf3otafixAssetTb4b4b4(
Te6edf3board Tff7b72= Ta5d6ff"Ta5d6ffpromicro_nrf52840Ta5d6ff"Tb4b4b4,
Te6edf3sha256 Tff7b72= Ta5d6ff"Ta5d6ff46ef3440f151d6f2606075bcd1aa83db25a660da7d25b988aeb47ef350c98794Ta5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
T8b949e/**
* Meshtastic `platformioTarget`s whose products OTAFIX lists as supported.
*
* A **UX gate only** β it decides whether the upgrade action is offered, never which image is written. Being wrong here
* costs a user a tap and an "unsupported board" message after the drive is read; it cannot flash anything. Every
* correctness decision is made by [otafixUf2ForBoardId] against the Board-ID on the drive.
*
* Derived from OTAFIX's "Boards supported" list. Deliberately excludes products that merely share a build target with a
* supported one β WISMESH Hub/Tap, Nomadstar Meteor Pro and RAK3401 all build against `wiscore_rak4631`, and T-Echo
* Plus/Lite against `t-echo`, but OTAFIX ships no bootloader for them.
*/
Tff7b72private Tff7b72val Te6edf3OTAFIX_SUPPORTED_TARGETSTb4b4b4: Te6edf3SetTff7b72<Tffa657StringTff7b72> Tff7b72=
Te6edf3setOfTb4b4b4(
Ta5d6ff"Ta5d6ffrak4631Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffrak_wismeshtagTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fft-echoTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffheltec-mesh-node-t114Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffnrf52_promicro_diy_tcxoTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffthinknode_m1Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffthinknode_m3Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffthinknode_m6Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fftracker-t1000-eTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffseeed_wio_tracker_L1Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffseeed_wio_tracker_L1_einkTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffseeed_solar_nodeTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffseeed_xiao_nrf52840_kitTa5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e/**
* The factory-erase image for [hardware], or `null` when none can be resolved safely.
*
* nRF52840 requires a resolved [DeviceHardware.softDeviceVariant]: the two images are linked for different application
* start addresses, and the UF2 bootloader's write guard begins at `MBR_SIZE`, so the wrong one erases a SoftDevice
* page. There is deliberately no default branch.
*/
Tff7b72internal Tff7b72fun Td2a8fferaseUf2ForTb4b4b4(Te6edf3hardwareTb4b4b4: Te6edf3DeviceHardwareTb4b4b4)Tb4b4b4: Te6edf3MaintenanceUf2? Tff7b72= Tff7b72when Tb4b4b4{
Te6edf3hardwareTb4b4b4.Te6edf3isRp2040Arc Tff7b72-Tff7b72> Te6edf3PICO_ERASE
Te6edf3hardwareTb4b4b4.Te6edf3isNrf52Arc Tff7b72-Tff7b72>
Tff7b72when Tb4b4b4(Te6edf3hardwareTb4b4b4.Te6edf3softDeviceVariantTb4b4b4) Tb4b4b4{
Te6edf3SoftDeviceVariantTb4b4b4.Te6edf3S140_6_1_1 Tff7b72-Tff7b72> Te6edf3ERASE_S140_6_1_1
Te6edf3SoftDeviceVariantTb4b4b4.Te6edf3S140_7_3_0 Tff7b72-Tff7b72> Te6edf3ERASE_S140_7_3_0
Tff7b72null Tff7b72-Tff7b72> Tff7b72null
Tb4b4b4}
Tff7b72else Tff7b72-Tff7b72> Tff7b72null
Tb4b4b4}
T8b949e/**
* True when OTAFIX lists a bootloader for [platformioTarget]'s product. UX gate only β see [OTAFIX_SUPPORTED_TARGETS].
*/
Tff7b72internal Tff7b72fun Td2a8ffotafixSupportsTargetTb4b4b4(Te6edf3platformioTargetTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3platformioTarget Tff7b72in Te6edf3OTAFIX_SUPPORTED_TARGETS
T8b949e/**
* The OTAFIX image matching the [boardId] a device reported in its `INFO_UF2.TXT`, or `null` when unrecognized.
*
* `null` refuses the upgrade. That is the correct outcome even for a board OTAFIX supports: an unrecognized Board-ID
* means the installed bootloader is not one we have a verified pairing for, and writing a bootloader built for other
* hardware is unrecoverable without SWD.
*/
Tff7b72internal Tff7b72fun Td2a8ffotafixUf2ForBoardIdTb4b4b4(Te6edf3boardIdTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3MaintenanceUf2? Tff7b72= Te6edf3OTAFIX_BY_BOARD_IDTff7b72[Te6edf3boardIdTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tff7b72]
T8b949e/** Board-IDs we ship an OTAFIX image for. Exposed for the uniqueness test. */
Tff7b72internal Tff7b72val Te6edf3otafixBoardIdsTb4b4b4: Te6edf3SetTff7b72<Tffa657StringTff7b72>
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3OTAFIX_BY_BOARD_IDTb4b4b4.Te6edf3keys
T8b949e/**
* Extracts the `Board-ID:` value from the contents of a UF2 bootloader's `INFO_UF2.TXT`.
*
* Format is fixed by `ghostfat.c`: `UF2 Bootloader <ver>` / `Model: <name>` / `Board-ID: <id>` / `Date: <date>`,
* CRLF-separated. Returns `null` when the line is absent, which means the volume is not an Adafruit-family UF2
* bootloader drive β itself a reason to refuse a destructive write.
*/
Tff7b72internal Tff7b72fun Td2a8ffparseUf2BoardIdTb4b4b4(Te6edf3infoUf2TextTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String? Tff7b72= Te6edf3infoUf2Text
Tb4b4b4.Te6edf3lineSequenceTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3firstOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3trimStartTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3startsWithTb4b4b4(Te6edf3UF2_BOARD_ID_PREFIXTb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4}
Tff7b72?.Te6edf3substringAfterTb4b4b4(Ta5d6ff':'Tb4b4b4)
Tff7b72?.Te6edf3trimTb4b4b4(Tb4b4b4)
Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tb4b4b4}
T8b949e/**
* Extracts the installed SoftDevice from the contents of a UF2 bootloader's `INFO_UF2.TXT`.
*
* `uf2_init()` appends this line at boot from `SD_ID_GET(MBR_SIZE)`/`SD_VERSION_GET(MBR_SIZE)` β i.e. read out of the
* MBR's registers β formatted as `SoftDevice: S<id> <major>.<minor>.<patch>`. Present in upstream Adafruit and in
* OTAFIX, and verified on a stock Seeed bootloader (`SoftDevice: S140 7.3.0`).
*
* This is the **authoritative** answer to the question the bundled map only estimates: not what the firmware was built
* against, but which SoftDevice is actually in flash. Returns `null` when the line is absent (very old bootloader),
* when no SoftDevice is installed, or when the id/version is not one we ship an erase image for.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4) T8b949e// guard clauses; an unparseable line must yield null rather than a guess
Tff7b72internal Tff7b72fun Td2a8ffparseUf2SoftDeviceTb4b4b4(Te6edf3infoUf2TextTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3SoftDeviceVariant? Tb4b4b4{
Tff7b72val Te6edf3value Tff7b72=
Te6edf3infoUf2Text
Tb4b4b4.Te6edf3lineSequenceTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3firstOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3trimStartTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3startsWithTb4b4b4(Te6edf3UF2_SOFTDEVICE_PREFIXTb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4}
Tff7b72?.Te6edf3substringAfterTb4b4b4(Ta5d6ff':'Tb4b4b4)
Tff7b72?.Te6edf3trimTb4b4b4(Tb4b4b4) Tff7b72?: Tff7b72return Tff7b72null
Tff7b72val Te6edf3parts Tff7b72= Te6edf3valueTb4b4b4.Te6edf3splitTb4b4b4(Ta5d6ff' 'Tb4b4b4)Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3partsTb4b4b4.Te6edf3size Tff7b72< T79c0ff2 Tff7b72|Tff7b72| Tff7b72!Te6edf3partsTff7b72[T79c0ff0Tff7b72]Tb4b4b4.Te6edf3equalsTb4b4b4(Te6edf3SUPPORTED_SOFTDEVICE_IDTb4b4b4, Te6edf3ignoreCase Tff7b72= Tff7b72trueTb4b4b4)Tb4b4b4) Tff7b72return Tff7b72null
Tff7b72return Te6edf3SoftDeviceVariantTb4b4b4.Te6edf3fromWireTb4b4b4(Te6edf3partsTff7b72[T79c0ff1Tff7b72]Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Which erase image a given variant needs. Total over the enum, so a new variant cannot silently reuse an old image.
*/
Tff7b72internal Tff7b72fun Td2a8fferaseUf2ForVariantTb4b4b4(Te6edf3variantTb4b4b4: Te6edf3SoftDeviceVariantTb4b4b4)Tb4b4b4: Te6edf3MaintenanceUf2 Tff7b72= Tff7b72when Tb4b4b4(Te6edf3variantTb4b4b4) Tb4b4b4{
Te6edf3SoftDeviceVariantTb4b4b4.Te6edf3S140_6_1_1 Tff7b72-Tff7b72> Te6edf3ERASE_S140_6_1_1
Te6edf3SoftDeviceVariantTb4b4b4.Te6edf3S140_7_3_0 Tff7b72-Tff7b72> Te6edf3ERASE_S140_7_3_0
Tb4b4b4}
T8b949e/** Outcome of reconciling the SoftDevice the drive reports against the bundled map. */
Tff7b72internal Tff7b72sealed Tff7b72interface T56d364EraseImageResolution Tb4b4b4{
T8b949e/** Safe to write [asset]; [variant] is the SoftDevice it is linked for. */
Tff7b72data Tff7b72class T56d364ResolvedTb4b4b4(Tff7b72val Te6edf3assetTb4b4b4: Te6edf3MaintenanceUf2Tb4b4b4, Tff7b72val Te6edf3variantTb4b4b4: Te6edf3SoftDeviceVariantTb4b4b4) Tb4b4b4: Te6edf3EraseImageResolution
T8b949e/**
* The drive and the bundled map disagree. Always a refusal: one of the two is wrong and we cannot tell which, and
* guessing writes an erase image into a SoftDevice. Also the signal that a map row needs correcting.
*/
Tff7b72data Tff7b72class T56d364ConflictTb4b4b4(Tff7b72val Te6edf3reportedTb4b4b4: Te6edf3SoftDeviceVariantTb4b4b4, Tff7b72val Te6edf3mappedTb4b4b4: Te6edf3SoftDeviceVariantTb4b4b4) Tb4b4b4: Te6edf3EraseImageResolution
T8b949e/** Neither source produced a variant. */
Tff7b72data Tff7b72object T56d364Unresolved Tb4b4b4: Te6edf3EraseImageResolution
Tb4b4b4}
T8b949e/**
* Picks the nRF erase image, preferring what the device reports over what the bundled map predicted.
*
* The map ([DeviceHardware.softDeviceVariant]) is a pre-flight hint β it decides whether the action is offered before
* any drive is mounted. Once the drive is readable its own report wins, because it comes from the MBR rather than from
* a hand-authored table. A disagreement refuses rather than picking a side.
*/
Tff7b72internal Tff7b72fun Td2a8ffresolveNrfEraseImageTb4b4b4(
Te6edf3mappedTb4b4b4: Te6edf3SoftDeviceVariant?Tb4b4b4,
Te6edf3reportedFromDriveTb4b4b4: Te6edf3SoftDeviceVariant?Tb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3EraseImageResolution Tff7b72= Tff7b72when Tb4b4b4{
Te6edf3reportedFromDrive Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3mapped Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3reportedFromDrive Tff7b72!Tff7b72= Te6edf3mapped Tff7b72-Tff7b72>
Te6edf3EraseImageResolutionTb4b4b4.Te6edf3ConflictTb4b4b4(Te6edf3reported Tff7b72= Te6edf3reportedFromDriveTb4b4b4, Te6edf3mapped Tff7b72= Te6edf3mappedTb4b4b4)
Te6edf3reportedFromDrive Tff7b72!Tff7b72= Tff7b72null Tff7b72-Tff7b72>
Te6edf3EraseImageResolutionTb4b4b4.Te6edf3ResolvedTb4b4b4(Te6edf3eraseUf2ForVariantTb4b4b4(Te6edf3reportedFromDriveTb4b4b4)Tb4b4b4, Te6edf3reportedFromDriveTb4b4b4)
T8b949e// No SoftDevice line: a bootloader older than the uf2_init that emits it. Fall back to the bundled hint.
Te6edf3mapped Tff7b72!Tff7b72= Tff7b72null Tff7b72-Tff7b72> Te6edf3EraseImageResolutionTb4b4b4.Te6edf3ResolvedTb4b4b4(Te6edf3eraseUf2ForVariantTb4b4b4(Te6edf3mappedTb4b4b4)Tb4b4b4, Te6edf3mappedTb4b4b4)
Tff7b72else Tff7b72-Tff7b72> Te6edf3EraseImageResolutionTb4b4b4.Te6edf3Unresolved
Tb4b4b4}
T8b949e/** The file every Adafruit-family UF2 bootloader exposes on its mass-storage volume. */
Tff7b72internal Tff7b72const Tff7b72val Te6edf3INFO_UF2_FILE_NAME Tff7b72= Ta5d6ff"Ta5d6ffINFO_UF2.TXTTa5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3UF2_BOARD_ID_PREFIX Tff7b72= Ta5d6ff"Ta5d6ffBoard-ID:Ta5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3UF2_SOFTDEVICE_PREFIX Tff7b72= Ta5d6ff"Ta5d6ffSoftDevice:Ta5d6ff"
T8b949e/** All Meshtastic nRF52840 boards run the S140 SoftDevice; anything else is out of scope and refuses. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3SUPPORTED_SOFTDEVICE_ID Tff7b72= Ta5d6ff"Ta5d6ffS140Ta5d6ff"
T8b949e/** UF2 block size, per the UF2 specification. */
Tff7b72internal Tff7b72const Tff7b72val Te6edf3UF2_BLOCK_BYTES Tff7b72= T79c0ff5T79c0ff1T79c0ff2
T8b949e/** Byte offset of `targetAddr` within a UF2 block header. */
Tff7b72internal Tff7b72const Tff7b72val Te6edf3UF2_TARGET_ADDR_OFFSET Tff7b72= T79c0ff1T79c0ff2
Tff7b72private Tff7b72const Tff7b72val Te6edf3UF2_MAGIC_START0 Tff7b72= T79c0ff0Te6edf3x0A324655
T8b949e/** Bytes in a little-endian 32-bit field, and the mask/shift used to reassemble one. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3UINT32_BYTES Tff7b72= T79c0ff4
Tff7b72private Tff7b72const Tff7b72val Te6edf3BITS_PER_BYTE Tff7b72= T79c0ff8
Tff7b72private Tff7b72const Tff7b72val Te6edf3BYTE_MASK Tff7b72= T79c0ff0Te6edf3xFFL
T8b949e/**
* Reads the target flash address of the first UF2 block in [bytes], or `null` when the payload is not a UF2 image.
*
* Used to cross-check a pinned erase image against the resolved SoftDevice variant before it is written.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4) T8b949e// guard clauses over a binary header
Tff7b72internal Tff7b72fun Td2a8ffuf2FirstTargetAddressTb4b4b4(Te6edf3bytesTb4b4b4: Te6edf3ByteArrayTb4b4b4)Tb4b4b4: Tffa657Long? Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3bytesTb4b4b4.Te6edf3size Tff7b72< Te6edf3UF2_BLOCK_BYTESTb4b4b4) Tff7b72return Tff7b72null
Tff7b72if Tb4b4b4(Te6edf3readLittleEndianUInt32Tb4b4b4(Te6edf3bytesTb4b4b4, T79c0ff0Tb4b4b4) Tff7b72!Tff7b72= Te6edf3UF2_MAGIC_START0Tb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return Tff7b72null
Tff7b72return Te6edf3readLittleEndianUInt32Tb4b4b4(Te6edf3bytesTb4b4b4, Te6edf3UF2_TARGET_ADDR_OFFSETTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffreadLittleEndianUInt32Tb4b4b4(Te6edf3bytesTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3offsetTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Long Tb4b4b4{
Tff7b72var Te6edf3value Tff7b72= T79c0ff0L
Tff7b72for Tb4b4b4(Te6edf3i Tff7b72in Te6edf3UINT32_BYTES Tff7b72- T79c0ff1 Te6edf3downTo T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3value Tff7b72= Tb4b4b4(Te6edf3value Te6edf3shl Te6edf3BITS_PER_BYTETb4b4b4) Te6edf3or Tb4b4b4(Te6edf3bytesTff7b72[Te6edf3offset Tff7b72+ Te6edf3iTff7b72]Tb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Te6edf3and Te6edf3BYTE_MASKTb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3value
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.08s